package mainBoerse;


import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import agent.PlayerAgent;
import interfaces.AccountManager;
import stockPrice.RandomStockPriceProvider;
import stockPrice.StockPriceProvider;
import transactions.BankStatement;
import exceptions.CreateDoublePlayerExeption;
import exceptions.NotEnoughMoneyException;
import exceptions.NotEnoughSharesToSell;
import exceptions.WrongParameterException;
import exceptions.WrongPlayerName;
import exceptions.WrongShareName;

public class AccountManagerImpl implements AccountManager {

	/**
	 */
	RandomStockPriceProvider provider_one = new RandomStockPriceProvider();
	/**
	 */
	StockPriceProvider providerStock = new StockPriceProvider() {
		@Override
		public void updateShareRates() {

		}

		@Override
		protected void updateShareRate(Share share) {

		}

		@Override
		public void startUpdate() {

		}
	};

	
	private static int countPlayers = 0;
	private List<player> playerList;
	private HashMap<String, ArrayList<BankStatement>> bankStatements;
	private static Logger log = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
	public static AccountManagerImpl instance = null;

	public static AccountManagerImpl getInstance() throws IOException {
		if (AccountManagerImpl.instance == null) {
			AccountManagerImpl.instance = new AccountManagerImpl("HSA");
		}
		return AccountManagerImpl.instance;
	}
	
	/**
	 * 
	 * @param firstPlayer first player to be created
	 * @throws IOException
	 */
	private AccountManagerImpl(String firstPlayer) throws IOException {
		// this.Player[getCountPlayers()] = new player(firstPlayer);
		setCountPlayers(getCountPlayers() + 1);
		this.playerList = new ArrayList<>();
		playerList.add(new player(firstPlayer));
		this.bankStatements = new HashMap<String, ArrayList<BankStatement>>();

	}
	
	/**
	 * @param name of the player
	 * 
	 */
	@Override
	public void createPlayer(String name) throws Exception {

		for (player listPlayer : playerList) {
			if (listPlayer != null) {
				if (listPlayer.getPlayerName().equals(name))
					throw new CreateDoublePlayerExeption(
							"Player existiert bereits! Waehlen Sie einen anderen Namen");
			}
		}

		playerList.add(new player(name));
	}

	// only for testing
	/*
	 * public player[] getPlayers() { return this.Player; }
	 */

	public List<player> getPlayerList() {
		return this.playerList;
	}

	public String printPlayer() {
		StringBuilder builder = new StringBuilder();
		/*
		 * for (int i = 0; i < Player.length; i++) { if (Player[i] != null)
		 * builder.append(Player[i].getPlayerName()); }
		 */

		// loop through arraylist with iterator
		Iterator<player> iterator = playerList.iterator();
		while (iterator.hasNext()) {
			player obj = iterator.next();
			builder.append(obj.getPlayerName());
		}

		return builder.toString();
	}

	private int searchPlayer(String playerName) throws WrongPlayerName {
		int searchPlayer = 0;
		boolean found = false;
		while (!found) {
			if (this.playerList.get(searchPlayer).getPlayerName()
					.equals(playerName)) {
				found = true;
			} else {
				if ((searchPlayer + 1 < playerList.size())
						&& (this.playerList.get(searchPlayer + 1)
								.getPlayerName() != null))
					searchPlayer++;
				else
					throw new WrongPlayerName("Wrong Playername");
			}
		}

		/*
		 * for(player player : playerList) {
		 * if(player.getPlayerName().equals(playerName)) found = true; else
		 * searchPlayer++; } if(!found) throw new
		 * WrongPlayerName("Wrong Playername");
		 */

		return searchPlayer;
	}

	public player getSpecificPlayer(String name) {
		// return this.Player[searchPlayer(name)];
		return this.playerList.get(searchPlayer(name));
	}

	@Override
	public void sellShare(String playerName, String shareName, int countShares)
			throws NotEnoughSharesToSell, WrongShareName, WrongPlayerName {
		int searchPlayer = 0;
		try {
			searchPlayer = searchPlayer(playerName);
		} catch (Exception e) {
			throw e;
		}
		float course = providerStock.getCurrentShareRate(shareName);

		try {
			// this.Player[searchPlayer].reduceSharesInItem(countShares,this.Player[searchPlayer].searchShare(shareName));

			this.playerList.get(searchPlayer).reduceSharesInItem(countShares,
					this.playerList.get(searchPlayer).searchShare(shareName));

			// System.out.println("Verkauf erfolgreich");
		} catch (NotEnoughSharesToSell e) {
			throw e;
		}

		// this.Player[searchPlayer].addMoney(countShares, course); // wohin ?
		this.playerList.get(searchPlayer).addMoney(countShares, course);
		BankStatement statement = new BankStatement(playerName, shareName,
				true, countShares, course, new Timestamp(new Date().getTime()));
		addStatementToBankStatements(playerName, statement);

	}

	private void addStatementToBankStatements(String mapKey,
			BankStatement statement) {

		ArrayList<BankStatement> list = this.bankStatements.get(mapKey);

		if (list == null) {
			list = new ArrayList<>();
			list.add(statement);
			this.bankStatements.put(mapKey, list);
		} else {
			this.bankStatements.get(mapKey).add(statement);
		}
		
		if(log.getLevel().intValue() >= Level.FINE.intValue()) {
			StringBuilder builder = new StringBuilder();
			builder.append(statement.toString());
			log.log(Level.FINE,builder.toString());
		}

	}

	@Override
	public void buyShare(String playerName, String shareName, int countShares)
			throws Exception {

		int searchPlayer = -1;
		float course;
		try {
			searchPlayer = searchPlayer(playerName);
		} catch (Exception e) {
			throw e;

		}

		try {
			course = providerStock.getCurrentShareRate(shareName);
		} catch (WrongShareName e1) {

			throw e1;
		}
		try {
			try {
				/*
				 * this.Player[searchPlayer].reduceMoney(countShares, course);
				 * this.Player[searchPlayer].buyShare(countShares, course,
				 * this.Player[searchPlayer].searchShare(shareName));
				 */

				this.playerList.get(searchPlayer).reduceMoney(countShares,
						course);
				this.playerList.get(searchPlayer).buyShare(
						countShares,
						course,
						this.playerList.get(searchPlayer)
								.searchShare(shareName));

			} catch (NotEnoughMoneyException e) {
				throw e;
			}
		} catch (IllegalArgumentException e) {
			throw e;
		}
		BankStatement statement = new BankStatement(playerName, shareName,
				false, countShares, course, new Timestamp(new Date().getTime()));
		
		addStatementToBankStatements(playerName, statement);
	}

	// @Override
	public float countValueAsset(String playerName, String assetName)
			throws WrongPlayerName {
		int searchPlayer = searchPlayer(playerName);
		float value = 0;
		// Asset[] assets = this.Player[searchPlayer].getAssets();
		Asset[] assets = this.playerList.get(searchPlayer).getAssets();
		for (Asset ass : assets) {
			if (ass.getName().equals(assetName)) {
				value = ass.getValue();
				break;
			}
		}
		// System.out.println(value);
		return value;

	}

	// @Override
	public float countValueAllAsset(String playerName) throws WrongPlayerName {
		int searchPlayer = 0;
		try {
			searchPlayer = searchPlayer(playerName);
		} catch (WrongPlayerName e) {
			throw e;
		}
		// return this.Player[searchPlayer].getTotalAssetValue();
		return this.playerList.get(searchPlayer).getTotalAssetValue();
	}

	public float getShareCourse(String shareName) throws WrongShareName {

		return provider_one.getCurrentShareRate(shareName);
	}

	// @Override
	public String getAllSharesAndCoursesOfAllPlayersToString(ResourceBundle prop) {
		StringBuilder allCourses = new StringBuilder();

		for (player player : playerList) {
			allCourses.append("<br><br>" + player.getPlayerName() + "<br>");
			allCourses.append(player.toString(prop));
			allCourses.append("TotalAsseValue: " + player.getTotalAssetValue() + " " + prop.getString("currency"));
		}

		// System.out.println(allCourses.toString());

		return allCourses.toString();
	}

	public static int getCountPlayers() {
		return countPlayers;
	}

	public static void setCountPlayers(int countPlayers) {
		AccountManagerImpl.countPlayers = countPlayers;
	}

	public void start() throws Exception {
		provider_one.startUpdate();
	}

	@Override
	public void playerAgent(String playername) throws Exception {
		getSpecificPlayer(playername).activateAgent();
		PlayerAgent agent = new PlayerAgent(playername);

		agent.process();
		// how to implement timer ?

	}

	@Override
	public void deactivatePlayerAgent(String playername) throws Exception {
		getSpecificPlayer(playername).deactivateAgent();
	}

	@Override
	public void getBankStatement(String playername, String param,String mimetype, int countStatements) throws WrongParameterException, FileNotFoundException, UnsupportedEncodingException {
		try{
			searchPlayer(playername);
		} catch (WrongPlayerName e) {
			throw e;
		}
		Integer test = countStatements;
		if(test == null) countStatements = 0;
		
		ArrayList<BankStatement> statements = this.bankStatements
				.get(playername);
		ArrayList<Share> allShares = StockPriceProvider.getSHARES();
		
		PrintWriter printWriter = null;
		if(mimetype.equals("HTML")) {
			printWriter = new PrintWriter("statements_"+playername + ".html", "UTF-8");
			printWriter.write("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'  'http://www.w3.org/TR/html4/loose.dtd'><html><head><title>Beschreibung der Seite</title></head><body><table border='1'>");
		} else if(mimetype.equals("TEXT")) {
			printWriter = new PrintWriter(System.out);
		} else throw new WrongParameterException("wrong paramter on position 3 'text' or 'html' expected");
		
		if (param.equals("O")) {
			Collections.sort(statements);
			ArrayList<BankStatement> orderedList = new ArrayList<>();
			
			for (Share share : allShares) {
				ArrayList<BankStatement> buffer = new ArrayList<>();
				for (int i = 0; i < statements.size(); i++) {
					if (statements.get(i).getShareName()
							.equals(share.getAktienName())) {
						buffer.add(statements.get(i));
					}
				}
				Collections.sort(buffer, new TimeComparator());
				orderedList.addAll(buffer);

			}
			printStatements(orderedList, countStatements, mimetype, printWriter);
		} else if (param.equals("T")) {
			Collections.sort(statements, new TimeComparator());
			printStatements(statements, countStatements, mimetype, printWriter);
		} else if(isInArray(allShares, param)) {
			ArrayList<BankStatement> onlyForParam = new ArrayList<>();
			for(BankStatement st : statements) {
				if(st.getShareName().equals(param.toUpperCase())) {
					onlyForParam.add(st);
				}
			}
			Collections.sort(onlyForParam, new TimeComparator());
			printStatements(onlyForParam, countStatements, mimetype, printWriter);
		}
		else {
			throw new WrongParameterException("Wrong Paramater on Position 3");
		}

		

	}
	
	private void printStatements(ArrayList<BankStatement> statements, int countStatements, String mime, PrintWriter writer) {
		if(countStatements == 0) countStatements = statements.size();
		else if(countStatements > statements.size()) countStatements = statements.size();
		
		if(mime.equals("HTML")) {
			for(int i = 0; i < countStatements; i++ ) {
				writer.write("<tr><td>" + statements.get(i).toString() + "</td></tr>");
			}
			writer.write("</table></body></html>");
			writer.close();
		} else {
			for(int i = 0; i < countStatements; i++ ) {
				System.out.print(statements.get(i).toString());
			}
		}
	}
	
	private boolean isInArray(ArrayList<Share> shares, String param) {
		
		boolean isInArray = false;
		for(Share share : shares) {
			if(param.toUpperCase().equals(share.getAktienName())) isInArray = true;
		}
		
		
		return isInArray;
		
	}
	
	@Override
	public void addShare(String shareName) throws WrongShareName {
		
		for(Share share : StockPriceProvider.getSHARES()) {
			if(shareName.equals(share.getAktienName())) throw new WrongShareName("Share already exists");
		}
		
		StockPriceProvider.addNewShare(shareName);
		for(player player : playerList) {
			player.addItem(shareName);
		}
		log.log(Level.FINE,"added Share during runtime " + shareName);
	}



}

class TimeComparator implements Comparator<BankStatement> {

	@Override
	public int compare(BankStatement o1, BankStatement o2) {

		return o1.getTimeStamp().compareTo(o2.getTimeStamp());
	}

}
